/* $ZEL: sis1100_init.c,v 1.7 2006/02/14 19:51:17 wuestner Exp $ */

/*
 * Copyright (c) 2001-2004
 * 	Peter Wuestner.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "sis1100_sc.h"

void
sis1100_dump_glink_status(struct sis1100_softc* sc, char* text, int locked)
{
    u_int32_t v;
    if (!locked) SEM_LOCK(sc->sem_hw);
    pINFO(sc, "%s:", text);
    pINFO(sc, "  ident       =%08x", sis1100readreg(sc, ident));
    pINFO(sc, "  sr          =%08x", sis1100readreg(sc, sr));
    pINFO(sc, "  cr          =%08x", sis1100readreg(sc, cr));
    pINFO(sc, "  t_hdr       =%08x", sis1100readreg(sc, t_hdr));
    pINFO(sc, "  t_am        =%08x", sis1100readreg(sc, t_am));
    pINFO(sc, "  t_adl       =%08x", sis1100readreg(sc, t_adl));
    pINFO(sc, "  t_dal       =%08x", sis1100readreg(sc, t_dal));
    pINFO(sc, "  tc_hdr      =%08x", sis1100readreg(sc, tc_hdr));
    pINFO(sc, "  tc_dal      =%08x", sis1100readreg(sc, tc_dal));
    pINFO(sc, "  p_balance   =%08x", sis1100readreg(sc, p_balance));
    pINFO(sc, "  prot_error  =%08x", sis1100readreg(sc, prot_error));
    pINFO(sc, "  d0_bc       =%08x", sis1100readreg(sc, d0_bc));
    pINFO(sc, "  d0_bc_buf   =%08x", sis1100readreg(sc, d0_bc_buf));
    pINFO(sc, "  d0_bc_blen  =%08x", sis1100readreg(sc, d0_bc_blen));
    pINFO(sc, "  d_hdr       =%08x", sis1100readreg(sc, d_hdr));
    pINFO(sc, "  d_am        =%08x", sis1100readreg(sc, d_am));
    pINFO(sc, "  d_adl       =%08x", sis1100readreg(sc, d_adl));
    pINFO(sc, "  d_bc        =%08x", sis1100readreg(sc, d_bc));
    pINFO(sc, "  rd_pipe_buf =%08x", sis1100readreg(sc, rd_pipe_buf));
    pINFO(sc, "  rd_pipe_blen=%08x", sis1100readreg(sc, rd_pipe_blen));
    pINFO(sc, "");
    v=sis1100readreg(sc, opt_csr);
    pINFO(sc, "  opt_csr     =%08x", v);
    sis1100writereg(sc, opt_csr, v&0xc0f50000);
    pINFO(sc, "  opt_csr     =%08x", sis1100readreg(sc, opt_csr));
    if (!locked) SEM_UNLOCK(sc->sem_hw);
}

int
sis1100_flush_fifo(struct sis1100_softc* sc, const char* text, int silent)
{
    u_int32_t sr, special, data;
    int count=0;

    sis1100writereg(sc, cr, cr_transparent);
    mb_reg();
    sr=sis1100readreg(sc, sr);
    while (sr&(sr_tp_special|sr_tp_data)) {
        while (sr&sr_tp_data) {
            data=sis1100readreg(sc, tp_data);
            if (!silent) pINFO(sc, "data   =          0x%08x\n", data);
            sr=sis1100readreg(sc, sr);
            count++;
            if (count>100) {
                sis1100writereg(sc, cr, cr_transparent<<16);
                pINFO(sc, "too many data in fifo; giving up");
                return -1;
            }
        }
        while ((sr&(sr_tp_special|sr_tp_data))==sr_tp_special) {
            special=sis1100readreg(sc, tp_special);
            if (!silent) pINFO(sc, "special=0x%08x\n", special);
            sr=sis1100readreg(sc, sr);
            count++;
            if (count>100) {
                sis1100writereg(sc, cr, cr_transparent<<16);
                pINFO(sc, "too many data in fifo; giving up");
                return -1;
            }
        }
        if ((!silent) && (count>100)) {
            pINFO(sc, "too many data in fifo; switching to 'silent'");
            silent=1;
        }
        if (count>10000) {
            sis1100writereg(sc, cr, cr_transparent<<16);
            pINFO(sc, "too many data in fifo; giving up");
            return -1;
        }
    }
    sis1100writereg(sc, cr, cr_transparent<<16);
    if (count && silent)
        pINFO(sc, "flushed %d words from fifo", count);
    return 0;
}

static void
sis1100_set_swapping(struct sis1100_softc* sc, int swap)
{
    u_int32_t tmp;

    /*pINFO(sc, "set swap to %d", swap);*/
    tmp=plxreadreg(sc, BIGEND_LMISC_PROT_AREA);
    if (swap) {
        sis1100writereg(sc, cr, 0x8);
        plxwritereg(sc, BIGEND_LMISC_PROT_AREA, tmp|(3<<6));
    } else {
        sis1100writereg(sc, cr, 0x80000);
        plxwritereg(sc, BIGEND_LMISC_PROT_AREA, tmp&~(3<<6));
    }
}

void
sis1100_update_swapping(struct sis1100_softc* sc, const char* caller)
{
    int swap;
#if defined(__LITTLE_ENDIAN)
    int local_endian=0;
#elif defined(__BIG_ENDIAN)
    int local_endian=1;
#else
#   error UNKNOWN ENDIAN
#endif

    /*
    pINFO(sc, "local endian is %s; remote endian is %s; user swap is %d "
    "(called from %s)",
        local_endian?"big":"little",
        sc->remote_endian?"big":"little",
        sc->user_wants_swap,
        caller);
    */

    swap=0;
    if (local_endian) swap=!swap;
    if (sc->remote_endian) swap=!swap;
    if (sc->user_wants_swap) swap=!swap;
    sis1100_set_swapping(sc, swap);
}

int
sis1100_init(struct sis1100_softc* sc)
{
#define MIN_FV 5
#define MAX_FV 7

    u_int32_t typ, hv, fk, fv;
    int res, i;

    sc->local_ident=sis1100readreg(sc, ident);
    typ=sc->local_ident&0xff;
    hv=(sc->local_ident>>8)&0xff;
    fk=(sc->local_ident>>16)&0xff;
    fv=(sc->local_ident>>24)&0xff;

    if (typ!=1) {
    	pERROR(sc, "ident=08x%x; claims not to be a PCI Device", typ);
    	res=ENXIO;
    	goto raus;
    }
    pINFO(sc, "HW version %d; FW code %d; FW version %d", hv, fk, fv);
    /*pINFO(sc, "LAS1RR=0x%08x LAS1BA=0x%08x LBRD1=0x%08x\n",
        plxreadreg(sc, LAS1RR), plxreadreg(sc, LAS1BA),
        plxreadreg(sc, LBRD1));*/
    switch (sc->local_ident&0xffff00) { /* HW version and FW code */
    	case 0x010100: {
    	    if (fv<MIN_FV) {
                pERROR(sc, "Firmware version too old;"
                        " at least version %d is required.",
                        MIN_FV);
                res=ENXIO;
    	    	goto raus;
            }
            if (fv>MAX_FV)
                pINFO(sc, "Driver not tested with"
                        " firmware versions higher than %d.",
                        MAX_FV);
    	    if (sc->reg_size!=0x1000) {
    	    	pERROR(sc, "wrong size of space 0: 0x%lx instead of 0x1000",
                    (unsigned long)sc->reg_size);
    	    	res=ENXIO;
    	    	goto raus;
    	    }
	    pINFO(sc, "size of space 1: 0x%lx (%ld MByte)",
		(u_long)sc->rem_size, (u_long)sc->rem_size>>20);
        } break;
    	default:
    	    pERROR(sc, "Hard- or Firmware not known");
    	    res=ENXIO;
    	    goto raus;
    }

    /* reset all we can */
    sis1100writereg(sc, cr, cr_reset); /* master reset */
    sis1100writereg(sc, cr, cr_rem_reset); /* reset remote, ignore wether it exists */
    if (sis1100_flush_fifo(sc, "init", 0)) { /* clear local fifo */
        res=EIO;
        goto raus;
    }
    sis1100writereg(sc, cr, cr_reset); /* master reset again */
    sis1100_reset_plx(sc);             /* reset PLX */
    sis1100writereg(sc, p_balance, 0);
    sis1100readreg(sc, prot_error);

    /* sis1100_dump_glink_status(sc, "INITIAL DUMP"); */

    /* enable PCI Initiator-to-PCI Memory */
    plxwritereg(sc, DMRR, 0);
    plxwritereg(sc, DMLBAM, 0);
    plxwritereg(sc, DMPBAM, 1);

    sis1100writereg(sc, cr, 8); /* big endian */

    sc->got_irqs=0;
    for (i=0; i<=7; i++) sc->irq_vects[i].valid=0;
    sc->pending_irqs=0;
    sc->new_irqs=0;
    sc->doorbell=0;
    sc->lemo_status=0;

    /* enable IRQs */
    sis1100_disable_irq(sc, 0xffffffff, 0xffffffff);
    sis1100_enable_irq(sc, plxirq_pci|plxirq_mbox|plxirq_doorbell|plxirq_local,
	    irq_synch_chg|irq_inh_chg|irq_sema_chg|
	    irq_rec_violation|irq_reset_req);

    sc->user_wants_swap=0;
    sc->dsp_present=0;
    sc->ram_size=0;
    sc->remote_ident=0;
    sc->old_remote_hw=sis1100_hw_invalid;
    sc->remote_hw=sis1100_hw_invalid;
    sc->remote_endian=1;

    if ((sis1100readreg(sc, sr)&sr_synch)==sr_synch) {
    	sis1100_init_remote(sc);
    } else {
    	pINFO(sc, "init: remote interface not reachable");
    }
    res=0;

    raus:
    return res;
#undef MIN_FV
#undef MAX_FV
}

void
sis1100_done(struct sis1100_softc* sc)
{
    /* DMA Ch. 0/1: not enabled */
    plxwritereg(sc, DMACSR0_DMACSR1, 0);
    /* disable interrupts */
    plxwritereg(sc, INTCSR, 0);
}
